let gameId = 1;
const pockerTypes = ['spade', 'heart', 'diamond', 'club'];

/**
 * Game collection
 */
const gameMap = new Map();

/**
 * Player collection
 */
const playerMap = new Map();

// Message type
const messageTypes = [
  'message', // Notification message
  'game', // game message
];

const messageTypeMap = {};
for (const item of messageTypes) {
  messageTypes[item] = item;
}


// ============ 类 ============

/**
 * user info
 */
 class User {
  constructor(userName) {
    this.userName = userName;
    // player type: homeowner(Homeowner) or join(join room)
    this.type = 'join';
    this.ws = void 0;
    // pocker composition
    this.deck = void 0;

    // Game room: new Game()
    this.game = void 0;

    // GIN count
    this.winCount = 0;
    // games played count
    this.gameCount = 0;
  }

  /**
   * Send massage
   * @param {string} action message type
   * @param {any} data message body
   * @returns 
   */
  sendMessage(action, data) {
    if (!this.ws) {
      log().next(`ws is empty, can not send message.`).end();
      return false;
    }

    const nextData = Object.assign({}, data, {
      winCount: this.winCount,
      gameCount: this.gameCount,
      deck: this.deck,
      type: this.type,
      userName: this.userName,
      state: this.state
    });

    if (this.game) {
      nextData.darkDeck = this.game.darkDeck
        ? new Date(this.game.darkDeck.length)
        : void 0;

      nextData.lightDeck = this.game.lightDeck;
      nextData.gameState = this.game.state;

      if (this.game.homeowner) {
        nextData[this.game.homeowner.userName] = {
          state: this.game.homeowner.state,
          deck: this.game.homeowner.deck
            ? new Array(this.game.homeowner.deck.length)
            : void 0
        };
      }

      if (this.game.join) {
        nextData[this.game.join.userName] = {
          state: this.game.join.state,
          deck: this.game.join.deck
            ? new Array(this.game.join.deck.length)
            : void 0
        };
      }
    }

    this.ws.send(JSON.stringify({ action, data: nextData }));
  }

  // Win once
  win() {
    this.winCount++;
  }

  gamed() {
    this.gameCount++;
  }
}


class Game {
  constructor () {
    // game id(room id)
    this.id = Buffer.from((gameId++).toString()).toString('base64');
    this.join = void 0;
    this.homeowner = void 0;
    this.deck = void 0;
    // Game state: NEWGAME, WAITING
    // 新游戏，等待
    this.state = void 0;
    // DARK deck
    this.darkDeck = [];
    // LIGHT deck
    this.lightDeck = [];
  }

  start() {
    this.state = 'NEWGAME';
    this.homeowner.state = this.join.state = 'NEWGAME';
    const deck = this.shuffle(this.createDeck());
    printDeck(deck);
    this.deck = deck;

    const homeownerDeck = [];
    const joinDeck = [];
    const lightDeck = [];
    const darkDeck = [];

    // 发牌
    const num = 10 * 2; // 每人 10 张
    for (let i = 0, len = deck.length; i < len; i++) {
      const item = deck[i];

      if (i < num) {
        switch (i % 2) {
          case 0:
            item.state = this.homeowner.userName;
            homeownerDeck.push(item);
            break;
          case 1:
            item.state = this.join.userName;
            joinDeck.push(item);
            break;
        }
        continue;
      }
      
      if (i === num) {
        lightDeck.push(item);
        continue;
      }

      darkDeck.push(item);
    }

    this.lightDeck = lightDeck;
    this.darkDeck = darkDeck;
    this.homeowner.deck = homeownerDeck;
    this.join.deck = joinDeck;
    // homeowner PROCESSING, join WAITING
    this.homeowner.state = 'PROCESSING';
    this.join.state = 'WAITING';
    // Add the number of games once.
    this.homeowner.gamed();
    this.join.gamed();

    this.homeowner.sendMessage(messageTypeMap.game);
    this.join.sendMessage(messageTypeMap.game);
  }

  // Game over, clear deck.
  gameover() {
    this.state = 'GAMEOVER';
    this.deck = void 0;
    this.darkDeck = [];
    this.lightDeck = [];
  }


  /**
   * 游戏进程消息
   */
  processMessage(ws, data) {
    let user;
    let userDeck;
    if (ws === this.homeowner.ws) {
      user = this.homeowner;
      userDeck = this.homeownerDeck;
    } else if (ws === this.join.ws) {
      user = this.join;
      userDeck = this.joinDeck;
    }

    if (user.state !== 'PROCESSING') {
      log().next(`${user.userName}: 不是你的回合!`).next('Send an error?').end();
      user.sendMessage(messageTypeMap.game, { message: `${user.userName}: 不是你的回合!` });
      return false;
    }

    const { drawType, cardIndex } = data;
    let card;

    switch (data.state) {
      case 'DRAW':
        // 抽牌
        // 抽牌类型(牌堆 DARK / 亮牌 LIGHT) 和 位置(在对应列表中的下标)
        let deckList;
        if (drawType === 'DARK') {
          deckList = this.darkDeck;
          
          card = this.darkDeck[cardIndex];

          if (!card) {
            log().next(`位置 ${cardIndex} 超出牌堆。`).end();
            user.sendMessage(messageTypeMap.game, { message: `位置 ${cardIndex} 超出牌堆。` });
            break;
          }

          this.darkDeck.splice(cardIndex, 1);
        } else if (drawType === 'LIGHT') {
          deckList = this.lightDeck;

          card = this.lightDeck[cardIndex];

          if (!card) {
            log().next(`位置 ${cardIndex} 超出亮牌。`).end();
            user.sendMessage(messageTypeMap.game, { message: `位置 ${cardIndex} 超出亮牌。` });
            break;
          }

          this.lightDeck.splice(cardIndex, 1);
        }

        if (!deckList) {
          log().next('不能在堆牌和亮牌以外抽牌。').end();
          user.sendMessage(messageTypeMap.game, { message: '不能在堆牌和亮牌以外抽牌。' });
          break;
        }

        if (card) {
          card.state = user.userName;
          user.state = 'DISCARD';

          this.broadcastMessage(user, {
            draw: card,
            [this.homeowner.userName]: {
              deck: this.homeownerDeck
            },
            [this.join.userName]: {
              deck: this.joinDeck
            }
          });
        }
        break;
      
      case 'DISCARD':
        if (user.state !== 'DISCARD') {
          const message = `User ${user.userName} stae is ${user.state}, 不是 DISCARD 不能弃牌。`;
          log().next(message).end();
          user.sendMessage(messageTypeMap.game, { message });
          break;
        }
        // 弃牌
        if (user === this.homeowner) {
          card = this.homeownerDeck[cardIndex];

          if (!card) {
            log().next(`位置 ${cardIndex} 超出玩家手牌。`).end();
            user.sendMessage(messageTypeMap.game, { message: `位置 ${cardIndex} 超出玩家手牌。` });
            break;
          }

          this.homeownerDeck.splice(cardIndex);
        } else if (user === this.join) {
          card = this.joinDeck[cardIndex];

          if (!card) {
            log().next(`位置 ${cardIndex} 超出玩家手牌。`).end();
            user.sendMessage(messageTypeMap.game, { message: `位置 ${cardIndex} 超出玩家手牌。` });
            break;
          }

          this.joinDeck.splice(cardIndex);
        }

        if (card) {
          card.state = 'LIGHT';
          this.lightDeck.push(card);

          user.state = 'WAITING';
          if (user === this.homeowner) {
            this.join.state = 'PROCESSING';
          } else if (user === this.join) {
            this.homeowner.state = 'PROCESSING';
          }

          this.homeowner.sendMessage(messageTypeMap.game, { card: draw });
          this.join.sendMessage(messageTypeMap.game, { card: draw });
        }

        break;

      case 'GIN':
        const currentDeck = userDeck.slice(0).sort((a, b) => (a.count - b.count));
        
        let prevCard;
        let sameValueDeck = [];
        for (const item of currentDeck) {
          if (!prevCard) {
            prevCard = item;
            sameValueDeck.push(item);
            continue;
          }

          if (item.value === prevCard.value) {
            prevCard = item;
            sameValueDeck.push(item);
          } else {
            if (this.sameValueDeck.length >= 3) {
              break;
            }
            
            sameValueDeck = [];
            prevCard = item;
          }
        }

        // ['spade', 'heart', 'diamond', 'club']
        const sameColorList = [
          [], // spade
          [], // heart
          [], // diamond
          [] // club
        ];

        for (const item of currentDeck) {
          switch (item.type) {
            case 'spade':
              sameColorList[0].push(item);
              break;
            case 'heart':
              sameColorList[1].push(item);
              break;
            case 'diamond':
              sameColorList[2].push(item);
              break;
            case 'club':
              sameColorList[3].push(item);
              break;
          }
        }

        // 相同花色连续数字个数
        let sameColorCount = 0;
        for (const list of sameColorList) {
          sameColorCount = 0;
          list.reduce((accumulator, currentCard) => {
            if (sameColorCount < 3) {
              if (accumulator.value === currentCard.value - 1) {
                sameColorCount += 1;
              } else {
                sameColorCount = 0;
              }
            }
            
            return currentCard;
          });
        }

        if (sameValueDeck.length < 3 && sameColorCount < 3) {
          // 没有符合条件的 GIN 组。
          user.sendMessage(messageTypeMap.game, {
            message: '没有符合 GIN 的牌组。'
          });
          break;
        }

        this.gameover();

        user.state = 'VICTORY';
        if (user === this.homeowner) {
          this.join.state = 'DEFEAT';
        } else if (user === this.join) {
          this.homeowner.state = 'DEFEAT';
        }

        log().next(`玩家 ${user.userName} 赢了。`).end();
        user.win();
        break;
      
      case 'REGAME':
        user.state = 'WAITING';

        if (this.homeowner.state === 'WAITING' && this.join.state === 'WAITING') {
          this.start();
          break;
        }


        break;
      
      default:
        log().next(`Unkown game state: ${data.state}`).end();
        break;
    }
  }

  /**
   * 创建一副牌
   * @returns {Array}
   */
  createDeck() {
    const deck = [];
    for (let i = 1; i <= 13; i++) {
      for (const type of pockerTypes) {
        deck.push(new Card(i, type));
      }
    }

    return deck;
  }

  /**
   * 洗牌
   */
  shuffle(deck) {
    let random;

    for (let i = deck.length - 1; i >= 0; i--) {
      random = Math.floor(Math.random() * i);

      const temp = deck[i];
      deck[i] = deck[random];
      deck[random] = temp;
    }

    return deck;
  }

  

  /**
   * connect
   * @param {User} user
   */
  connect(user) {
    user.game = this;
    user.state = 'WAITING';
    if (user.type === 'homeowner') {
      this.homeowner = user;
    } else {
      this.join = user;
    }

    if (!this.join) {
      this.state = 'WAITING';
    } else if (this.homeowner && this.join) {
      this.state = 'NEWGAME';
    }

    if (this.homeowner) {
      this.homeowner.sendMessage(messageTypeMap.game);
    }

    if (this.join) {
      this.join.sendMessage(messageTypeMap.game);
    }

    if (this.state === 'NEWGAME') {
      // 开始游戏
      this.start();
    }
  }

  /**
   * disconnect 断开连接
   * @param {User} user
   */
  disconnect(user) {
    // Delete the corresponding user in the room.
    // 删除房间中的用户
    if (user.type === 'homeowner') {
      this.homeowner = void 0;
      if (this.join) {
        // 如果退出房间的是房主，且房间中还有玩家，玩家自动成为房主
        this.join.type = 'homeowner';
        this.homeowner = this.join;
        this.join = void 0;
      }
    } else if (user.type === 'join') {
      this.join = void 0;
    }

    log().next(`玩家 ${user.userName} 断开连接。`).end();

    if (!this.homeowner && !this.join) {
      this.state = 'CLOSE';
      // 房间中没有玩家，删除房间
      gameMap.delete(this.id);
      log().next('玩家退出游戏，关闭房间。').end();
    } else if (this.homeowner) {
      // 还有一个玩家
      this.state = 'WAITING';
      this.homeowner.state = 'WAITING';

      this.homeowner.sendMessage(messageTypeMap.game, { message: `${user.userName} 离开游戏。` });
    }
  }
}


/**
 * Card
 * @param {number} value 1 - 13: 1 - 10 and J(Jack) Q(Queen) K(King)
 * @param {string} type spade(黑桃) heart(红心（红桃）) diamond(方块) club(梅花)
 */
class Card {
  constructor(value, type) {
    // Initialize a deck of poker.
    // 1 - 10 and J(Jack) Q(Queen) K(King)
    this.types = pockerTypes;
    if (!this.types.includes(type)) {
      throw new Error('Card Type: %s has Error.', type);
    }

    if (!value || !value.toString().match(/^(?:[1-9]|1[0-3])$/)) {
      throw new Error('Card Value: %s has Error.', value);
    }

    this.value = Number(value);
    this.type = type;
    // Poker serial number: 1 spade: 1, 1 heart: 2, 1 diamond: 3, 1 club: 4,
    //                      2 spade: 5, 2 heart: 6, 2 diamond: 7, 2 club: 8 ...
    this._state = 'DARK';

    this.count = this.calculationCount();
  }

  // card state: DARK(Hidden card / heap), LIGHT(light card), username(Player card)
  get state() {
    return this._state;
  }
  set state(value) {
    this._state = value;
  }

  calculationCount() {
    const typeIndex = this.types.findIndex(item => item === this.type);
    return (this.value - 1) * 4 + typeIndex + 1;
  }
}


// ============ 类 end ============

/**
 * Init Game
 * @param {ws} ws
 */
 function initGame(ws) {
  ws.on('message', function (message) {
    try {
      const { data, action } = JSON.parse(message);
      console.log('initGame data: ', data);
      log().next(`Init Game: userName: ${data.userName}`).end();

      switch (action) {
        case 'connect':
          const userName = data.userName;
    
          let user = playerMap.get(userName);
          ws.userName = userName;

          if (!user) {
            user = new User(userName);
            playerMap.set(userName, user);
          }

          user.ws = ws;

          // Join or init game room.
          let game;

          // Join a room.
          if (data.playType !== 'homeowner') {
            user.type = data.playType;
            // 在集合中查找状态为 WAITING 的游戏
            for (const currentGame of gameMap.values()) {
              if (currentGame.state === 'WAITING') {
                game = currentGame;
              }
            }
          }

          // 如果没有可以加入的房间，则创建一个房间
          if (!game) {
            user.type = 'homeowner';
            game = new Game();

            gameMap.set(game.id, game);
          }

          game.connect(user);
          break;
        
        case 'game':
          for (const item of gameMap.values()) {
            if ((item.homeowner && item.homeowner.ws === ws) ||
              (item.join && item.join.ws === ws)) {
              item.processMessage(ws, data);
              break;
            }
          }
      }

    } catch (e) {
      console.log('============ start ============');
      console.log('Init Game: Deserialization error: %o', e);
      console.log('============ end ============');

      ws.send(JSON.stringify({
        action: messageTypeMap.message,
        data: 'Init Game: Deserialization error'
      }));
    }
  });

  ws.on('close', function (event) {
    for (const item of gameMap.values()) {
      if (item.homeowner && item.homeowner.ws === ws) {
        item.disconnect(item.homeowner);
        break;
      }
      if (item.join && item.join.ws === ws) {
        item.disconnect(item.join);
        break;
      }
    }
  });
}


/**
 * Log
 */
function log() {
  console.log('\n');
  console.log('============ start ============');
  const retMap = {
    next(data) {
      console.log(data);
      return retMap;
    },
    end() {
      console.log('============ end ============');
      console.log('\n');
    }
  };
  return retMap;
}


/**
 * Print deck
 */
function printDeck(deck) {
  let nextLog = log();
  for (let i = 0, len = deck.length; i < len; i++) {
    const item = deck[i]
    nextLog = nextLog.next(`Card: ${i} is ${item.type} ${item.value}.`);
  }

  nextLog.end();
}

module.exports = {
  initGame
};